package fr.cephb.operonj.tools.cmdline;


import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


import org.w3c.dom.Document;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

import fr.cephb.operonj.core.bio.ChromInfo;
import fr.cephb.operonj.core.db.ReplacePolicy;
import fr.cephb.operonj.core.db.database.BuildDB;
import fr.cephb.operonj.core.db.database.ChromDB;
import fr.cephb.operonj.core.db.database.GenomicSeqDB;
import fr.cephb.operonj.core.db.key.BuildChrom;
import fr.cephb.operonj.core.db.key.SequenceIndex;

public class ChromLoad extends AbstractApplication
	{
	private ReplacePolicy policy=ReplacePolicy.ERROR_IF_EXISTS;
	
	@XmlRootElement(name="segment")
	public static class ChromInput
		{
		@XmlAttribute(required=true)
		int id;
		@XmlAttribute(required=false)
		Integer length;
		@XmlElement(required=true,nillable=false)
		String name;
		@XmlAttribute(required=false)
		ChromInfo.FLAG flag=ChromInfo.FLAG.autosomal;
		@XmlElement(required=true,nillable=false)
		String filename;
		@Override
		public String toString()
			{
			return name+" "+filename+" "+flag;
			}
		}
	
	@XmlRootElement(name="segments")
	public static class ChromList
		{
		@XmlAttribute(required=true,name="build-id")
		int buildId;
		@XmlElement(name="segment")
		List<ChromInput> input=new ArrayList<ChromInput>(32);
		@Override
		public String toString()
			{
			StringBuilder b= new StringBuilder();
			b.append("build:"+buildId).append("\n");
			for(ChromInput i:this.input)
				{
				b.append("  ").append(i.toString()).append("\n");
				}
			return b.toString();
			}
		}
	
	@XmlRootElement(name="segments-list")
	public static class SegmentSet
		{
		@XmlElement(name="segments")
		List<ChromList> segments=new ArrayList<ChromList>();
		}
	
	private ChromLoad()
		{
		super.envCfg.setAllowCreate(true);
		super.envCfg.setReadOnly(false);
		super.envCfg.setTransactional(true);
		}
	
	
	@Override
	protected void usage(PrintStream out)
		{
		super.usage(out);
		out.println("Options:");
		out.println(" -policy <value="+policy+"> one of :");
		for(ReplacePolicy p:ReplacePolicy.values())
			{
			out.println("  \t"+p);
			}
		}
	
	@Override
	protected int processArg(String[] args, int optind)
		{
		int n= super.processArg(args, optind);
		if(n!=-1) return n;
		if(args[optind].equals("-policy"))
			{
			this.policy=ReplacePolicy.valueOf(args[++optind]);
			return optind;
			}
		return -1;
		}
	
	private void load(SegmentSet list) throws Exception
		{
		Transaction txn=null;
		for(int i=0;i< list.segments.size();++i)
			{
			ChromList cl1= list.segments.get(i);
			for(int j=i+1;j< list.segments.size();++j)
				{
				ChromList cl2= list.segments.get(j);
				if(cl1.buildId==cl2.buildId) throw new IllegalArgumentException("build-id "+cl2.buildId+" defined twice");
				}
			}
		try
			{
			txn=getEnvironment().beginTransaction(null, null);
			for(ChromList L: list.segments)
				{
				load(txn,L);
				}
			txn.commit();
			}
		catch (Exception e)
			{
			if(txn!=null) txn.abort();
			throw e;
			}
		}
	
	private void load(Transaction txn,ChromList list) throws Exception
		{
		
		DatabaseEntry key= new DatabaseEntry();
		DatabaseEntry data= new DatabaseEntry();
		
			for(int i=0;i< list.input.size();++i)
				{
				ChromInput chr1=list.input.get(i);
				
				File f= new File(chr1.filename);
				if(!f.exists() ||  !f.isFile())
					{
					throw new IllegalArgumentException(f.toString()+" is not a valid file");
					}
				for(int j=i+1;j< list.input.size();++j)
					{
					ChromInput chr2=list.input.get(j);
					
					if(chr1.id==chr2.id)
						{
						throw new IllegalArgumentException("Duplicate chromosome id");
						}
					if(chr1.name.equalsIgnoreCase(chr2.name))
						{
						throw new IllegalArgumentException("Duplicate chromosome names");
						}
					
					if(chr1.filename.equals(chr2.filename))
						{
						throw new IllegalArgumentException("Duplicate chromosome paths");
						}
					}
				}
			
			
			DatabaseConfig cfg=new DatabaseConfig();
			cfg.setAllowCreate(false);
			cfg.setReadOnly(true);
			cfg.setTransactional(true);
			BuildDB buildDb= new BuildDB(getEnvironment(), txn, null, cfg);
			if(!buildDb.containsKey(txn,list.buildId))
				{
				throw new IllegalArgumentException("unknown build.id:"+list.buildId);
				}
			buildDb.close();
			
			cfg=new DatabaseConfig();
			cfg.setAllowCreate(true);
			cfg.setReadOnly(false);
			cfg.setTransactional(true);
			ChromDB chromDB=new ChromDB(getEnvironment(), txn, null, cfg);
			Cursor c=null;
			LOG.info("removing old chromosome for given build.id="+list.buildId);
			c=chromDB.openCursor(txn);
			key=new DatabaseEntry();
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				if( chromDB.getKeyBinding().entryToObject(key).getBuildId()==list.buildId)
					{
					LOG.info("deleting old chromInfo");
					if(c.delete()!=OperationStatus.SUCCESS)
						{
						LOG.warning("Cannot delete!");
						}
					}
				}
			c.close();
			
			
			cfg=new DatabaseConfig();
			cfg.setAllowCreate(true);
			cfg.setReadOnly(false);
			cfg.setTransactional(true);
			GenomicSeqDB genomicDB= new GenomicSeqDB(
				getEnvironment(),
				txn,
				null,cfg );
			
			
			
			for(ChromInput input:list.input)
				{
				BuildChrom bc= new BuildChrom(list.buildId, input.id);
				System.err.println(input);
				
				LOG.info("deleting old genomicDB "+bc);
				c=genomicDB.openCursor(txn);
				key=new DatabaseEntry();
				while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
					{
					SequenceIndex si=genomicDB.getKeyBinding().entryToObject(key);
					if( !(si.getBuildId()==list.buildId &&
					      si.getChromId()==input.id
						)) continue;
				
					if(c.delete()!=OperationStatus.SUCCESS)
						{
						LOG.warning("Cannot delete!");
						}
					}
				c.close();
				
				
				LOG.info("Start Reading fasta "+input.filename);
				BufferedReader in= new BufferedReader(new InputStreamReader(openStream(input.filename)));
				int ch=in.read();
				if(ch!='>') throw new IOException("Expected > in "+input.filename);
				while((ch=in.read())!=-1)
					{
					if(ch=='\n') break;
					}
				if(ch!='\n')  throw new IOException("Expected a CRLF in header of "+input.filename);
				
				int seqLoc=0;
				byte buffer[]=new byte[SequenceIndex.SIZE];
				int buffSize=0;
				while(true)
					{
					ch=in.read();
					
					if(ch!=-1)
						{
						if(Character.isWhitespace(ch)) continue;
						if(!Character.isLetter(ch)) throw new IOException("Bad letter:"+(char)ch);
						buffer[buffSize]=(byte)ch;
						buffSize++;
						}
					if(ch==-1 || buffSize==SequenceIndex.SIZE)
						{
						if(buffSize>0)
							{
							SequenceIndex seqIndex= new SequenceIndex(bc,seqLoc);
							byte array2[];
							if(buffSize!=SequenceIndex.SIZE)
								{
								array2=new byte[buffSize];
								System.arraycopy(buffer, 0, array2, 0, buffSize);
								}
							else
								{
								array2=buffer;
								}
							if(genomicDB.put(txn, seqIndex, array2)!=OperationStatus.SUCCESS)
								{
								LOG.warning("Cannot insert!");
								}
							}
						seqLoc+=buffSize;
						buffSize=0;
						if(ch==-1) break;
						}
					}
				in.close();
				LOG.info("End reading fasta "+input.filename);
				LOG.info("sizeof("+input.filename+")="+seqLoc);
				
				
				ChromInfo chromInfo= new ChromInfo(list.buildId,input.id,input.name,
						(input.length==null?0:input.length),
						input.flag);
				LOG.info("inserting "+bc);
				if(chromDB.put(txn, bc, chromInfo)!=OperationStatus.SUCCESS)
					{
					LOG.warning("Cannot insert:"+bc+"/"+chromInfo);
					}
				}
			
			genomicDB.close();
			chromDB.close();
			
		}
	
	private int run(String args[]) throws Exception
		{
		DocumentBuilderFactory f=DocumentBuilderFactory.newInstance();
		f.setCoalescing(true);
		f.setNamespaceAware(false);
		f.setValidating(false);
		f.setExpandEntityReferences(true);
		f.setIgnoringComments(true);
		f.setIgnoringElementContentWhitespace(true);
	
		DocumentBuilder docBuilder= f.newDocumentBuilder();
		Document dom;
		
		JAXBContext context = JAXBContext.newInstance(SegmentSet.class);
		Unmarshaller unmarshaller=context.createUnmarshaller();
		unmarshaller.setSchema(getSchema());
		
		
		int optind= processArgs(args);
		open();
		if(optind==args.length)
			{
			LOG.info("Reading stdin");
			dom=docBuilder.parse(System.in);
			SegmentSet list=(SegmentSet)unmarshaller.unmarshal(dom);
			load(list);
			}
		else
			{
			while(optind<args.length)
				{
				String fname=args[optind++];
				LOG.info("Reading "+fname);
				InputStream in= openStream(fname);
				dom=docBuilder.parse(in);
				in.close();
				SegmentSet list=(SegmentSet)unmarshaller.unmarshal(dom);
				load(list);
				}
			}
		close();
		return 0;
		}
	
	
	public static void main(String[] args)
		{
		try
			{
			ChromLoad app= new ChromLoad();
			app.run(args);
			}
		catch (Exception err)
			{
			err.printStackTrace();
			}
		}
	}
